/*
 * ******************************************************************************
 *   Copyright (c) 2013 Roman Nurik, 2013-2014 Gabriele Mariotti.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *  *****************************************************************************
 */

package it.gmariotti.cardslib.library.view.listener;


import it.gmariotti.cardslib.library.AnimatorListenerAdapter;
import it.gmariotti.cardslib.library.Constants;
import it.gmariotti.cardslib.library.ResourceTable;
import it.gmariotti.cardslib.library.internal.CardArrayAdapter;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.utils.PacMap;
import ohos.utils.Sequenceable;

import java.io.IOException;

import static ohos.agp.components.Component.HIDE;
import static ohos.agp.components.Component.VISIBLE;

/**
 * It is based on Roman Nurik code.
 * See this link for original code:
 * https://code.google.com/p/romannurik-code/source/browse/#git%2Fmisc%2Fundobar
 */
public class UndoBarController {

    private Component mBarView;
    private Text mMessageView;
    private AnimatorProperty mBarAnimator;
    private EventHandler mHideHandler = new EventHandler(EventRunner.create());

    private UndoListener mUndoListener;
    private UndoBarHideListener mUndoBarHideListener;

    // State objects
    private Sequenceable mUndoToken;
    private CharSequence mUndoMessage;

    private UndoBarUIElements mUndoBarUIElements;

    /**
     * Interface to listen the undo controller actions
     */
    public interface UndoListener {
        /*
         *  Called when you undo the action
         */
        void onUndo(Sequenceable undoToken);
    }

    /**
     * Interface to listen for when the Undo controller hides the Undo Bar,
     * after it times out from being shown for the currently dismissed mUndoToken.
     */
    public interface UndoBarHideListener {
        /**
         * Called when the UndoBar is hidden after being shown.
         *
         * @param undoOccurred true if the user pressed the undo button
         *                     for the current mUndoToken.
         */
        void onUndoBarHide(boolean undoOccurred);
    }

    public UndoBarController(Component undoBarView, UndoListener undoListener) {
        this(undoBarView, undoListener, null);
    }

    public UndoBarController(Component undoBarView, UndoListener undoListener, UndoBarUIElements undoBarUIElements) {
        mBarView = undoBarView;
        mBarAnimator = mBarView.createAnimatorProperty();
        mUndoListener = undoListener;

        if (undoBarUIElements == null)
            undoBarUIElements = new DefaultUndoBarUIElements();
        mUndoBarUIElements = undoBarUIElements;

        mMessageView = (Text) mBarView.findComponentById(mUndoBarUIElements.getUndoBarMessageId());
        mBarView.findComponentById(mUndoBarUIElements.getUndoBarButtonId())
                .setClickedListener(component -> {
                    hideUndoBar(false);
                    mUndoListener.onUndo(mUndoToken);
                    // Remove the reference to the undo token, since the undo has occurred.
                    mUndoToken = null;
                });

        setupAnimation();

        if (mUndoBarUIElements.isEnabledUndoBarSwipeAction() != UndoBarUIElements.SwipeDirectionEnabled.NONE) {
            setupSwipeActionOnUndoBar();
        }

        hideUndoBar(true);
    }


    public void showUndoBar(boolean immediate, CharSequence message,
                            Sequenceable undoToken, UndoBarHideListener undoBarHideListener) {

        // We're replacing the existing UndoBarHideListener, meaning that
        // the original object removal was not undone. So, execute
        // onUndoBarHide for the previous listener.
        if (mUndoBarHideListener != null) {
            mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
        }


        mUndoToken = undoToken;
        mUndoMessage = message;
        mUndoBarHideListener = undoBarHideListener;
        mMessageView.setText(mUndoMessage.toString());

        mHideHandler.removeTask(mHideRunnable);
        try {
            mHideHandler.postTask(mHideRunnable,
                    mBarView.getResourceManager().getElement(ResourceTable.Integer_list_card_undobar_hide_delay).getInteger());
        } catch (Exception e) {
             HiLog.error(Constants.LABEL_LOG,e.toString());
        }
        mBarView.setVisibility(VISIBLE);
        if (immediate) {
            mBarView.setAlpha(1);
        } else {

            if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.ALPHA) {
                mBarAnimator.cancel();
                mBarAnimator
                        .alpha(1)
                        .setDuration(200);
            } else if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.TOPBOTTOM) {
                mBarAnimator.cancel();
                mBarAnimator.moveFromY(mBarView.getTop())
                        .moveToY(0)
                        .alpha(1)
                        .setDuration(400);

            }
        }
    }

    public void hideUndoBar(boolean immediate) {
        mHideHandler.removeTask(mHideRunnable);
        if (immediate) {
            mBarView.setVisibility(HIDE);
            mBarView.setAlpha(0);
            mUndoMessage = null;
            if (mUndoBarHideListener != null) {
                // The undo has occurred only if mUndoToken was set to null.
                mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
            }
            mUndoBarHideListener = null;
            mUndoToken = null;
        } else {
            mBarAnimator.cancel();

            if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.ALPHA) {
                mBarAnimator
                        .alpha(0)
                        .setDuration(200)
                        .setStateChangedListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onEnd(Animator animator) {
                                mBarView.setVisibility(HIDE);
                                mUndoMessage = null;
                                if (mUndoBarHideListener != null) {
                                    // The undo has occurred only if mUndoToken was set to null.
                                    mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
                                }
                                mUndoBarHideListener = null;
                                mUndoToken = null;
                            }
                        });
            } else if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.TOPBOTTOM) {
                mBarAnimator.moveFromY(mBarView.getTop())
                        .moveToY(+mBarView.getHeight())
                        .alpha(0)
                        .setDuration(200)
                        .setStateChangedListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onEnd(Animator animation) {
                                mBarView.setVisibility(HIDE);
                                mUndoMessage = null;
                                if (mUndoBarHideListener != null) {
                                    // The undo has occurred only if mUndoToken was set to null.
                                    mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
                                }
                                mUndoBarHideListener = null;
                                mUndoToken = null;
                            }
                        });
            }
        }
    }

    public void onSaveInstanceState(PacMap outState) {
        outState.putString("undo_message", mUndoMessage.toString());
        outState.putSequenceableObject("undo_token", mUndoToken);
    }

    public void onRestoreInstanceState(PacMap savedInstanceState) {
        if (savedInstanceState != null) {
            mUndoMessage = savedInstanceState.getString("undo_message");
            mUndoToken = savedInstanceState.getSequenceable("undo_token").get();

            if (mUndoToken != null || mUndoMessage != null) {
                showUndoBar(true, mUndoMessage, mUndoToken, mUndoBarHideListener);
            }
        }
    }

    private Runnable mHideRunnable = new Runnable() {
        @Override
        public void run() {
            hideUndoBar(false);
        }
    };

    public Sequenceable getUndoToken() {
        return mUndoToken;
    }

    private void setupAnimation() {
        if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.TOPBOTTOM) {
            mBarView.setTranslationY(mBarView.getHeight());
        }
    }

    private void setupSwipeActionOnUndoBar() {
        if (mBarView != null) {

            if (mUndoBarUIElements.isEnabledUndoBarSwipeAction() == UndoBarUIElements.SwipeDirectionEnabled.LEFTRIGHT) {

                mBarView.setTouchEventListener(new SwipeDismissTouchListener(mBarView, null,
                        new SwipeDismissTouchListener.DismissCallbacks() {
                            @Override
                            public boolean canDismiss(Object token) {
                                return mUndoBarUIElements.isEnabledUndoBarSwipeAction() != UndoBarUIElements.SwipeDirectionEnabled.NONE;
                            }

                            @Override
                            public void onDismiss(Component view, Object token) {
                                hideUndoBar(true);
                                mUndoListener.onUndo(mUndoToken);
                                // Remove the reference to the undo token, since the undo has occurred.
                                mUndoToken = null;
                            }
                        }));
            } else if (mUndoBarUIElements.isEnabledUndoBarSwipeAction() == UndoBarUIElements.SwipeDirectionEnabled.TOPBOTTOM) {

                mBarView.setTouchEventListener(new SwipeDismissTopBottomTouchListener(mBarView, null,
                        new SwipeDismissTopBottomTouchListener.DismissCallbacks() {
                            @Override
                            public boolean canDismiss(Object token) {
                                return mUndoBarUIElements.isEnabledUndoBarSwipeAction() != UndoBarUIElements.SwipeDirectionEnabled.NONE;
                            }

                            @Override
                            public void onDismiss(Component view, Object token) {
                                hideUndoBar(true);
                                mUndoListener.onUndo(mUndoToken);
                                // Remove the reference to the undo token, since the undo has occurred.
                                mUndoToken = null;
                            }
                        }));
            }
        }

    }

    // -------------------------------------------------------------
    //  Undo Custom Bar
    // -------------------------------------------------------------

    /**
     * Interface to set the ui elements in undo bar
     */
    public interface UndoBarUIElements {

        /**
         * UndoBar id
         *
         * @return int
         */
        public int getUndoBarId();

        /**
         * TextView Id which displays message
         *
         * @return int
         */
        public int getUndoBarMessageId();

        /**
         * UndoButton Id
         *
         * @return int
         */
        public int getUndoBarButtonId();

        /**
         * UndoMessage.
         * Implement this method to customize the undo message dynamically.
         * </p>
         * You can't find the cards with these positions in your arrayAdapter because the cards are removed.
         * You have to/can use your id itemIds, to identify your cards.
         *
         * @param cardArrayAdapter array Adapter
         * @param itemIds          ids of items
         * @param itemPositions    position of removed items
         * @return String
         */
        public String getMessageUndo(CardArrayAdapter cardArrayAdapter, String[] itemIds, int[] itemPositions);

        /**
         * Define the swipe action to remove the undobar.
         *
         * @return SwipeDirectionEnabled
         */
        public SwipeDirectionEnabled isEnabledUndoBarSwipeAction();

        /**
         * Define the animation type for the undobar when it appears.
         *
         * @return AnimationType
         */
        public AnimationType getAnimationType();


        /**
         * Enum to define the animation type of the undobar.<p/>
         * Use the {@link AnimationType#ALPHA} for an alpha animation, or {@link AnimationType#TOPBOTTOM} for a translation from bottom to top.
         */
        public enum AnimationType {
            ALPHA(0),
            TOPBOTTOM(1);

            private final int mValue;

            private AnimationType(int value) {
                mValue = value;
            }

            public int getValue() {
                return mValue;
            }
        }

        /**
         * Enum to define the direction of the swipe action.
         * <p/>
         * You can use {@link SwipeDirectionEnabled#NONE}  to disable the swipe action or  {@link SwipeDirectionEnabled#LEFTRIGHT} to enable an action in left-right direction
         * or {@link SwipeDirectionEnabled#TOPBOTTOM} to define a swipe action from top to bottom.
         */
        public enum SwipeDirectionEnabled {
            NONE(0),
            LEFTRIGHT(1),
            TOPBOTTOM(2);

            private final int mValue;

            private SwipeDirectionEnabled(int value) {
                mValue = value;
            }

            public int getValue() {
                return mValue;
            }
        }

    }

    /**
     * Default UndoBar
     * <p>
     * You can provide a custom UndoBar.
     * This UndoBar has to contains these elements:
     * <ul>
     *    <li>A TextView</li>
     *    <li>A Button</li>
     *    <li>A root element with an id attribute </li>
     * </ul>
     * <p>
     *  You should use the same Ids provided in the default layout list_card_undo_message,
     *  but if you have to use different ids you can use the CardArrayAdapter.setUndoBarUIElements.
     * <p>
     *  Example:
     *  <code>
     *      mCardArrayAdapter.setUndoBarUIElements(new UndoBarController.DefaultUndoBarUIElements(){
     * <p>
     *          //Override methods to customize the elements
     *      }
     *  </code>
     * It is very important to set the UndoBarUIElements before to call the setEnableUndo(true);
     */
    public static class DefaultUndoBarUIElements implements UndoBarUIElements {

        public DefaultUndoBarUIElements() {
        }

        ;

        @Override
        public int getUndoBarId() {
            return ResourceTable.Id_list_card_undobar;
        }

        @Override
        public int getUndoBarMessageId() {
            return ResourceTable.Id_list_card_undobar_message;
        }

        @Override
        public int getUndoBarButtonId() {
            return ResourceTable.Id_list_card_undobar_button;
        }

        @Override
        public String getMessageUndo(CardArrayAdapter cardArrayAdapter, String[] itemIds, int[] itemPositions) {
            if (cardArrayAdapter != null && cardArrayAdapter.getContext() != null) {
                ResourceManager res = cardArrayAdapter.getContext().getResourceManager();
                if (res != null) {
                    try {
                        return res.getElement(ResourceTable.Plural_list_card_undo_items).getPluralString(itemPositions.length, itemPositions.length);
                    } catch (Exception e) {
                         HiLog.error(Constants.LABEL_LOG,e.toString());
                    }
                }
            }
            return null;
        }

        @Override
        public SwipeDirectionEnabled isEnabledUndoBarSwipeAction() {
            return SwipeDirectionEnabled.NONE;
        }

        @Override
        public AnimationType getAnimationType() {
            return AnimationType.ALPHA;
        }

    }

    ;


    /**
     * Sets UndoBar UI Elements
     *
     * @return UndoBarUIElements
     */
    public UndoBarUIElements getUndoBarUIElements() {
        return mUndoBarUIElements;
    }

    /**
     * Returns UndoBar UI Elements
     *
     * @param undoBarUIElements
     */
    public void setUndoBarUIElements(UndoBarUIElements undoBarUIElements) {
        this.mUndoBarUIElements = undoBarUIElements;
    }
}
